package decoder

import (
	"io/ioutil"
	"fmt"
	"encoding/json"
	"os"
	"errors"
	"math"
	"dcmgo/cost"
	"strings"
	"strconv"
	"dcmgo/img"
)

type DcmEntry struct {
	reader *DicomReader
}

func newDcmEntry() *DcmEntry {
	reader := newDicomReader()
	return &DcmEntry{reader}
}

func (this *DcmEntry) readDcm(dcmName string, isOutJson, isOutImg bool, jsonName, jpgName string) ([]byte, *DcmEntry, error) {
	data, err := ioutil.ReadFile(dcmName)
	this.reader.Init(data, 0)
	// header 旧版本前8位校验签名
	this.reader.skip(128)
	//DICM
	dicm := this.reader.readHeader()
	if dicm != "DICM" {
		err = errors.New("illegal dicom file")
		return data, this, err
	}

	dicomJson := newDicomJson()
	var elements []*DicomElement
	elements = this.readDataElements(dicomJson)
	dicomJson.SetElements(elements)

	if isOutJson {
		this.outConsole(dicomJson, true)
		if jsonName == "" {
			jsonName = strings.Replace(dcmName, ".dcm", ".json", 1)
		}
		err = this.outJson(jsonName, dicomJson, false)
		fmt.Printf("\n%s success output to %s", dcmName, jsonName)
	}

	if isOutImg {
		if jpgName == "" {
			jpgName = strings.Replace(dcmName, ".dcm", ".png", 1)
		}
		err = this.outImageJpg(jpgName, dicomJson)
		fmt.Printf("\n%s success output jpg to %s", dcmName, jpgName)
	}

	return data, this, err
}

func (this *DcmEntry) outConsole(dicomJson *DicomJson, pretty bool) error {
	if pretty {
		rs, err := json.MarshalIndent(dicomJson, "", "  ")
		fmt.Printf("%s", rs)
		return err
	} else {
		rs, err := json.Marshal(dicomJson)
		fmt.Printf("%s", rs)
		return err
	}
}

func (this *DcmEntry) outImageJpg(fileName string, dicomJson *DicomJson) error {
	os.Remove(fileName)
	file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0666)
	defer file.Close()
	pixelData := img.GetCompression(dicomJson.PixelData, dicomJson.TransferSyntax)
	m := img.ReadImage(pixelData, nil, dicomJson.Photometric, dicomJson.SamplePerPixel, dicomJson.Rows, dicomJson.Columns, dicomJson.WindowWidth, dicomJson.WindowCenter, dicomJson.isBigEndian)
	err = img.SaveAs(file, m)
	return err
}

func (this *DcmEntry) outJson(fileName string, dicomJson *DicomJson, pretty bool) error {
	os.Remove(fileName)
	file, _ := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0666)
	defer file.Close()
	encoder := json.NewEncoder(file)
	if pretty {
		encoder.SetIndent("", " ")
	}
	err := encoder.Encode(dicomJson)
	return err
}

func (this *DcmEntry) readDataElements(dicomJson *DicomJson) (elements []*DicomElement) {
	elements = make([]*DicomElement, 0)
	isExplicitVR, isPixel, isLittleEndian, isSigned := true, false, true, false
	var charset string
	for !this.reader.Finished() {
		var element *DicomElement
		element = this.readElement(isExplicitVR, isPixel, isLittleEndian, isSigned, charset)
		if element.Tag[0] == "0002" && element.Tag[1] == "0010" {
			dicomJson.TransferSyntax = element.VF
		}
		if element.Tag[0] == "0028" {
			switch element.Tag[1] {
			case "0002":
				dicomJson.SamplePerPixel = this.getInt(element.VF)
			case "0004":
				dicomJson.Photometric = element.VF
			case "0006":
				dicomJson.PlannerConfiguration = this.getInt(element.VF)
			case "0008":
				dicomJson.NumberOfFrames = this.getInt(element.VF)
			case "0010":
				dicomJson.Rows = this.getInt(element.VF)
			case "0011":
				dicomJson.Columns = this.getInt(element.VF)
			case "0100":
				dicomJson.BitsAllocated = this.getInt(element.VF)
			case "0101":
				dicomJson.BitsStored = this.getInt(element.VF)
			case "0102":
				dicomJson.HighBit = this.getInt(element.VF)
			case "0103":
				dicomJson.PixelRepresentation = this.getInt(element.VF)
			case "1050":
				dicomJson.WindowCenter = this.getFirstInt(element.VF)
			case "1051":
				dicomJson.WindowWidth = this.getFirstInt(element.VF)
			case "3010":
				for _, item := range element.Items {
					if item.Tag[0] == "fffe" && item.Tag[1] == "e000" && item.VL != 4 {
						for _, lut := range item.Items {
							if lut.Tag[0] == "0028" && lut.Tag[1] == "3006" {
								dicomJson.LutNormal = lut.Data
							}
						}
					}
				}
			}
		}
		if element.Tag[0] == "7fe0" && element.Tag[1] == "0010" {
			if len(element.Items) == 0 {
				dicomJson.PixelData = element.Data
			}
			for _, item := range element.Items {
				if item.Tag[0] == "fffe" && item.Tag[1] == "e000" && item.VL != 4 {
					dicomJson.PixelData = item.Data
				}
			}
		}
		elements = append(elements, element)
	}
	return elements
}

func (this *DcmEntry) getInt(str string) int {
	i, _ := strconv.ParseInt(str, 10, 64)
	return int(i)
}

func (this *DcmEntry) getFirstInt(str string) int {
	is := strings.Split(str, "\\")
	i, _ := strconv.ParseInt(is[0], 10, 64)
	return int(i)
}

func (this *DcmEntry) readElement(isExplicitVR, isPixel, isLittleEndian, isSigned bool, charset string) (element *DicomElement) {
	element = newDicomElement()
	/** 数据结构 dataElement */
	//TAG 4字节
	tag := this.reader.readTag()

	//VR 2字节
	//Value Field 根据VL确定字节长度
	//Value Length 4字节
	var vr, vf string
	var vl, isDir int
	var data []byte

	if tag[0] == "0010" && charset == "" {
		charset = cost.GB18030
	}

	vr, vl, vf, data = this.reader.readVRAndVLAndValue(tag, charset, isExplicitVR, isPixel, isLittleEndian, isSigned, isDir)
	vf = strings.TrimRight(vf, " ")

	if tag[0] == "0002" && tag[1] == "0010" {
		switch vf {
		case cost.ExplictVRLittleEndian: //显示little
			isLittleEndian = true
			isExplicitVR = true
		case cost.ExplictVRBigEndian: //显示big
			isLittleEndian = false
			isExplicitVR = true
		case cost.ImplictVREndian: //隐式little
			isLittleEndian = true
			isExplicitVR = false
		}
	} else if tag[0] == "0008" && tag[1] == "0005" {
		switch vf {
		case cost.ISO_IR_6:
			charset = cost.GB18030
		case cost.Big5:
			charset = cost.Big5
		case cost.GB18030:
			charset = cost.GB18030
		case cost.UTF8:
			charset = cost.UTF8
		}
	}

	element.Tag = tag
	element.VR = vr
	element.VL = vl
	element.VF = vf
	element.Data = data

	for isDir > 0 {
		tag = this.reader.readTag()
		if (vr == "SQ" && vl == math.MaxUint32) || (tag[0] == "fffe" && tag[1] == "e000" && vl == math.MaxUint32) {
			vr, vl, vf, data = this.reader.readVRAndVLAndValue(tag, charset, false, isPixel, isLittleEndian, isSigned, isDir)
		} else {
			vr, vl, vf, data = this.reader.readVRAndVLAndValue(tag, charset, true, isPixel, isLittleEndian, isSigned, isDir)
		}

		item := newDicomElement()
		item.Tag = tag
		item.VR = vr
		item.VL = vl
		item.VF = vf
		item.Data = data

		element.AddItems(item)
		if isDir == 2 {
			break
		}
	}

	return element
}
